/*
Copyright 2010,2011 Matt Van Der Westhuizen

This file is part of CGLL.

CGLL is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

CGLL is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with CGLL.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.chaoticengine.cgll.input;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.chaoticengine.cgll.entity.Entity;
import org.chaoticengine.cgll.entity.EntityManager;
import org.chaoticengine.cgll.input.Command.Axis;
import org.newdawn.slick.Input;
import org.newdawn.slick.KeyListener;
import org.newdawn.slick.MouseListener;

/**
 * An InputManager instance is used to handle all input events for one
 * CGLLGameState, by calling the corresponding Command as configured an
 * XML file loaded by that instance in the for of an InputConfiguration object.
 *
 * @author Matt v.d. Westhuizen
 */
public class InputManager implements MouseListener, KeyListener {
    private Map<Integer, Binding> keyboard = new HashMap<Integer, Binding>();
    private Map<Integer, Binding> mouse = new HashMap<Integer, Binding>();

    private Input input = null;
    private EntityManager em = null;
    private InputConfiguration config = null;
    private boolean enabled = true;
    private float scrW = 0.0f;
    private float scrH = 0.0f;
    private long lastMouseMoveTime = 0;

    public InputManager(EntityManager em) {
        this.em = em;
        scrW = this.em.getContainer().getWidth();
        scrH = this.em.getContainer().getHeight();
        lastMouseMoveTime = this.em.getContainer().getTime();
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public void setConfiguration(InputConfiguration inputCfg) {
        this.config = inputCfg;

        Iterator<Binding> bIt = config.getBindings().iterator();
        while (bIt.hasNext()) {
            Binding b = bIt.next();
            InputDevice i = b.getInput();

            if (i instanceof KeyboardInputDevice) {
                keyboard.put(i.getKeyCode(), b);
            } else if (i instanceof MouseInputDevice) {
                mouse.put(i.getKeyCode(), b);
            }
        }
    }

    public InputConfiguration getConfiguration() {
        return(config);
    }

    // Mouse support methods
    public void mousePressed(int button, int x, int y) {
        if (!enabled) return;

        Binding b = mouse.get(button);

        if (b != null) {
            Command c = getCommand(b.getCommandName());
            if (c != null) {
                c.setMouseX(x);
                c.setMouseY(y);
                c.enable();
            }
        }
    }

    public void mouseReleased(int button, int x, int y) {
        if (!enabled) return;

        Binding b = mouse.get(button);

        if (b != null) {
            Command c = getCommand(b.getCommandName());
            if (c != null) {
                c.setMouseX(x);
                c.setMouseY(y);
                c.disable();
            }
        }
    }

    public void mouseWheelMoved(int change) {
        if (!enabled) return;

        Binding b = null;
        if (change > 0) {
            b = mouse.get(MouseInputDevice.WHEEL_UP);
        } else if (change < 0) {
            b = mouse.get(MouseInputDevice.WHEEL_DOWN);
        }

        if (b != null) {
            Command c = getCommand(b.getCommandName());

            if (c != null) {
                c.enable();
                c.doAction(1);
                c.disable();
            }
        }
    }

    public void mouseClicked(int button, int x, int y, int clickCount) {
    }

    private void handleMouseAxis(Axis axis, boolean positive, Binding b, 
            int pixels, float dimension, int x, int y) {
        if (b != null) {
            Command cmd = getCommand(b.getCommandName());

            if (cmd != null) {
                if ((pixels == 0) && (cmd.isEnabled())) {
                    cmd.disable();
                } else {
                    if (((positive) && (pixels > 0)) ||
                            ((!positive) && (pixels < 0))) {
                        cmd.setAxis(axis);
                        cmd.setAxisPixels(axis, cmd.getAxisPixels(axis) + pixels);
                        cmd.setAxisPercentage(axis, Math.abs(pixels) / dimension);
                        cmd.setMouseX(x);
                        cmd.setMouseY(y);

                        cmd.enable();
                    }
                }
            }
        }
    }

    public void mouseMoved(int oldX, int oldY, int newX, int newY) {
        if (!enabled) return;

        int xPixels = newX - oldX;
        int yPixels = newY - oldY;

        long time = this.em.getContainer().getTime();
        int delta = (int) (time - lastMouseMoveTime);
        lastMouseMoveTime = time;

        int axis = MouseInputDevice.X_AXIS_MINUS;
        handleMouseAxis(Axis.X, false, mouse.get(axis), xPixels, scrW, newX, newY);

        axis = MouseInputDevice.X_AXIS_PLUS;
        handleMouseAxis(Axis.X, true, mouse.get(axis), xPixels, scrW, newX, newY);

        axis = MouseInputDevice.Y_AXIS_MINUS;
        handleMouseAxis(Axis.Y, false, mouse.get(axis), yPixels, scrH, newX, newY);

        axis = MouseInputDevice.Y_AXIS_PLUS;
        handleMouseAxis(Axis.Y, true, mouse.get(axis), yPixels, scrH, newX, newY);
    }

    public void mouseDragged(int oldX, int oldY, int newX, int newY) {
        if (!enabled) return;

        int xPixels = newX - oldX;
        int yPixels = newY - oldY;

        long time = this.em.getContainer().getTime();
        int delta = (int) (time - lastMouseMoveTime);
        lastMouseMoveTime = time;

        int axis = MouseInputDevice.X_AXIS_MINUS;
        handleMouseAxis(Axis.X, false, mouse.get(axis), xPixels, scrW, newX, newY);

        axis = MouseInputDevice.X_AXIS_PLUS;
        handleMouseAxis(Axis.X, true, mouse.get(axis), xPixels, scrW, newX, newY);

        axis = MouseInputDevice.Y_AXIS_MINUS;
        handleMouseAxis(Axis.Y, false, mouse.get(axis), yPixels, scrH, newX, newY);

        axis = MouseInputDevice.Y_AXIS_PLUS;
        handleMouseAxis(Axis.Y, true, mouse.get(axis), yPixels, scrH, newX, newY);
    }

    // Keyboard support methods
    public void keyPressed(int key, char c) {
        if (!enabled) return;

        Binding b = keyboard.get(key);

        if (b != null) {
            Command cmd = getCommand(b.getCommandName());

            if (cmd != null) {
                cmd.enable();
            }
        }
    }

    public void keyReleased(int key, char c) {
        if (!enabled) return;

        Binding b = keyboard.get(key);

        if (b != null) {
            Command cmd = getCommand(b.getCommandName());

            if (cmd != null) {
                cmd.disable();
            }
        }
    }

    // Misc methods
    public void setInput(Input input) {
        this.input = input;
    }

    public boolean isAcceptingInput() {
        return(enabled);
    }

    public void inputStarted() {
    }

    public void inputEnded() {
    }

    // Utility methods
    private Command getCommand(String name) {
        if (name == null) {
            return(null);
        }

        Entity player = em.getPlayer();

        if (player == null) {
            return(null);
        }

        Map<String, Command> commands = player.getComponentManager().getCommands();
        Command cmd = commands.get(name);

        return(cmd);
    }
}
